package download

import (
	"context"
	"errors"
	"sync"
	"time"

	"hundsun.com/hsl/hschain/common/util"

	"github.com/libp2p/go-libp2p-core/peer"
	"hundsun.com/hsl/hschain/common/types"
	"hundsun.com/hsl/hschain/p2p/common/constant"
	"hundsun.com/hsl/hschain/p2p/impl/dht/protocol"
	pbledger "hundsun.com/hsl/hschain/protos/ledger"
	pbp2p "hundsun.com/hsl/hschain/protos/p2p"
	"hundsun.com/hsl/hschain/store/mq"
)

var once sync.Once

func init() {
	protocol.RegisterProtocolIniter(InitProtocol)
}

// Protocol ...
type Protocol struct {
	*protocol.P2PEnv
}

// InitProtocol initials protocol
func InitProtocol(env *protocol.P2PEnv) {
	protocolInstance := &Protocol{}
	protocolInstance.Init(env)
}

//Init 初始化
func (p *Protocol) Init(env *protocol.P2PEnv) {
	p.P2PEnv = env
	once.Do(func() {
		//注册p2p通信协议，用于处理节点之间请求
		protocol.RegisterStreamHandler(env.P2pNode.Host, constant.DownloadBlockAddress, p.P2pNode.SyncMsgResp(p.handleStreamDownloadBlock))
		// 注册事件处理函数
		protocol.RegisterEventHandler(types.EventFetchBlock, p.handleEventDownloadBlock)
	})
}

func (p *Protocol) downloadBlock(height uint64, tasks tasks) error {

	var retryCount uint
	tasks.Sort() //对任务节点时延进行排序，优先选择时延低的节点进行下载
ReDownload:
	select {
	case <-p.Ctx.Done():
		p.Log.Warn("downloadBlock", "process", "done")
		return p.Ctx.Err()
	default:
		break
	}

	if tasks.Size() == 0 {
		return errors.New("no peer for download")
	}

	retryCount++
	if retryCount > 50 {
		return errors.New("beyond max try count 50")
	}

	task := p.availbTask(tasks, height)
	if task == nil {
		time.Sleep(time.Millisecond * 400)
		goto ReDownload
	}

	var downloadStart = time.Now().UnixNano()
	block, err := p.downloadBlockFromPeer(height, task.Pid)
	if err != nil {
		p.Log.Errorf("handleEventDownloadBlock failed, reason:%v, pid:%s, height:%d", err, task.Pid, height)
		p.releaseJob(task)
		tasks = tasks.Remove(task)
		goto ReDownload
	}
	remotePid := task.Pid.Pretty()
	costTime := (time.Now().UnixNano() - downloadStart) / 1e6

	p.Log.Debugf("download block from pid:%s, blockNumber:%d, blockSize(bytes):%d, costTime %d ms",
		remotePid, block.GetHeader().GetNumber(), block.XXX_Size(), costTime)

	var blocks []*pbledger.Block
	blocks = append(blocks, block)
	msg := p.TopicClient.NewMessage(types.BlockChainModule, mq.ModeTopic.String(), types.EventBroadcastAddBlock, blocks)
	_ = p.TopicClient.Send(msg, false)

	p.releaseJob(task)
	return nil
}

func (p *Protocol) downloadBlockFromPeer(height uint64, pid peer.ID) (*pbledger.Block, error) {
	_, cancel := context.WithTimeout(p.Ctx, time.Second*10)
	defer cancel()

	request := &pbp2p.GetBlocksRequest{
		Start: height,
		End:   height + 1,
	}
	buf, _ := util.Struct2Bytes(request)
	req := p.P2pNode.NewP2pMessage("", "", constant.MsgGetBlock, buf, nil)
	//todo debug
	response, err := p.P2pNode.SyncMsg(req, pid, constant.DownloadBlockAddress)
	if err != nil || response == nil {
		return nil, err
	}

	var blkResp = &pbp2p.GetBlocksResponse{}
	_, err = util.Bytes2Struct(blkResp, response.Payload)
	if err != nil {
		return nil, err
	}

	return blkResp.Blocks[0], nil
}
